Coin Change II
Let's solve the Coin Change II problem using Dynamic Programming.
Statement#
Suppose you are given a list of coins and a certain amount of money. Each coin in the list is unique and of a different denomination. You are required to count the number of ways the provided coins can sum up to represent the given amount. If there is no possible way to represent that amount, then return 0.
Note: You may assume that for each combination you make, you have an infinite number of each coin. In simpler terms, you can use a specific coin as many times as you want.
Let's say you have only two coins,
3 coins of
cents: . 1 coin of
cents and 1 coin of cents:
Constraints:
1 <=
coins.length<= 3001 <=
coins[i]<= 5000All the coins have a unique value.
0 <=
amount<= 5000
Examples#
Let's see a few more examples to get a better understanding of the problem statement:
No. | Coins | Amount | Number of ways |
1 | [1, 2, 5] | 7 | 6 |
2 | [1, 5, 10, 25] | 10 | 4 |
3 | [7] | 9 | 0 |
4 | [9,10,11] | 0 | 1 |
Try it yourself#
Implement your solution in the following playground.
Note: If you clicked the “Submit” button and the code timed out, this means that your solution needs to be optimized in terms of running time.
Hint: Use dynamic programming and see the magic.
Solution#
We will first explore the naive recursive solution to this problem and then see how it can be improved using the Unbounded Knapsack dynamic programming pattern.
Naive approach#
A naive approach to solve this problem would be to make all the possible combinations of coins or generate all subsets of coin denominations that sum up to the required amount.
While making the combinations, a point to keep in mind is that we should try to avoid repeatedly counting the same combinations. For example,
As a base case, we return
The slides below will help you visualize the process.
1 of 7
2 of 7
3 of 7
4 of 7
5 of 7
6 of 7
7 of 7
Now, let's look at the code in the code widget below for the solution we just discussed.
Note: Please observe that if you include the test case commented out in the driver code, the solution is likely to time out. Try to solve the larger problem using the dynamic programming solutions provided below and see the difference.
Time complexity#
If the length of the list coins is
Space complexity#
The space complexity for this algorithm is
Optimized solution using dynamic programming#
As the recursive solution to this problem is very costly, let's see if we can reduce this cost in any way. Dynamic programming helps us avoid recomputing the same subproblems. Now let's analyze our recursive solution to see if it has the properties needed for conversion to dynamic programming.
Optimal substructure: If we wanted to find the solution to the problem of counting an amount,
, given different coins, and we knew the answer to the subproblems formed by subtracting each of the coins from the amount , we could simply sum up the answer to these subproblems, and that would give us the answer to our original problem. Therefore, this problem obeys the optimal substructure property. Overlapping subproblems: The algorithm solves the same subproblems again and again, so it also has the second property of dynamic programming.
Top-down solution#
The top-down solution, commonly known as the memoization technique, is an enhancement of the recursive solution. It overcomes the problem of calculating solutions to subproblems over and over again by storing the subproblem solutions, and reusing the stored solutions when the same subproblem is encountered again. Therefore, the only addition to this algorithm compared to the simple recursive one is the addition of memoization. We store all the results in memo and then retrieve them as needed. Since we had two defining variables here, the amount, and the maximum value, we use them to uniquely identify each subproblem.
Note: Please observe that if you now uncomment the large test case in the driver code, the dynamic programming solution will still work.
Time complexity#
Let's see how many unique subproblems are possible that we can evaluate. The value of amount will not be greater than the one provided at the start of the algorithm, let’s call it maximum variable will therefore be bounded by
Space complexity#
The space required to hold the results of these subproblems would be
Bottom-up solution#
Now let’s look at the bottom-up dynamic programming approach to solving this problem. The bottom-up solution, also known as the tabulation technique, is an iterative method of solving dynamic programming problems. To make up an amount using
Here, we first create a table
First, as per the base case, for the amount
Next, we fill up each cell of our table using the unbounded knapsack dynamic programming pattern. In the pattern, for each cell, we need the sum of two values we get from two of our choices:
Use the coin: We only use the coin if
. If we pick , provided , then the amount reduces by . Now, we need the number of ways to make this remaining amount using coins . We already have this value present at . Don't use the coin: We don't use the coin if
. If we don't pick , given , then we have to figure out the number of ways to make the amount using only coins . This value is already present at .
Let’s look at the following illustration to get a better understanding of the algorithm:
1 of 10
2 of 10
3 of 10
4 of 10
5 of 10
6 of 10
7 of 10
8 of 10
9 of 10
10 of 10
Let's have a look at the code for the bottom-up approach as well.
Note: Please observe that if you now uncomment the large test case in the driver code, the dynamic programming solution will still work.
Time complexity#
As apparent from the visualization as well, we are iterating over a table of size
Space complexity#
The space complexity of this algorithm is
Can we do better?#
You must have noticed in the above algorithm that to fill up a column, we only require the previous column’s values; that is, for filling the column against the
We can further improve this approach by using a lookup array of length
The time complexity would remain the same,
Minimum Coin Change
Introduction to Recursive Numbers